/*
 *	xmalloc.c
 *	  part of the xlibrary by SunDEV (http://sundev.890m.com)
 *
 *	Changelog :
 *	  ##-##-## : First public release
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
#include "xcommon.h"

#include "xmalloc.h"

void handleoutofmem(const char *text, size_t size, const char *allocfunc, u32 line) {
	printf("ERROR:Out of memory with %s|%dB in \"%s:%d\"\n", allocfunc+1, size, text, line);
	while(1);
}

#if defined(XMALLOC_DEBUG_WRITE)
#include <stdarg.h>
void writeDBG(char *format, ...) {
	if(!format) return;

	FILE *fd=fopen("/debug.txt", "a+");
	if(!fd) return;

	va_list args;
	va_start(args, format);
	vfprintf(fd, format, args);
	va_end(args);
	fclose(fd);
}

void *__xmalloc(size_t size, const char *text, u32 line);
void *__xcalloc(size_t num, size_t size, const char *text, u32 line);
void *__xrealloc(void *ptr, size_t size, const char *text, u32 line);
void __xfree(void *ptr, const char *text, u32 line);

void *_xmalloc(size_t size, const char *text, u32 line) {
    void *ret=__xmalloc(size, text, line);

    writeDBG("m(%d)=%#x, %s:%d\n", size, ret, text, line);

    return ret;
}

void *_xcalloc(size_t num, size_t size, const char *text, u32 line) {
    void *ret=__xcalloc(num, size, text, line);

    writeDBG("c(%d,%d)=%#x, %s:%d\n", num, size, ret, text, line);

    return ret;
}

void *_xrealloc(void *ptr, size_t size, const char *text, u32 line) {
    void *ret=__xrealloc(ptr, size, text, line);

    writeDBG("r(%#x, %d)=%#x, %s:%d\n", ptr, size, ret, text, line);

    return ret;
}

void _xfree(void *ptr, const char *text, u32 line) {
    __xfree(ptr, text, line);

    writeDBG("f(%#x), %s:%d\n", ptr, text, line);
}

#   define XMALLOC_DECL(fname) (_##fname)
#else
#   define XMALLOC_DECL(fname) (fname)
#endif

#if defined(XMALLOC_DEBUG_CHECK)
typedef struct {
	void *buf;
	size_t size;
} sM;
sM *alloc=NULL;
size_t alloc_size=0;
#define ALLOC_GRAIN 32
size_t alloc_len=0;
size_t alloc_num=0;

#define MAGIC (0xff)
#define MAGICL 4

typedef struct {
	u8 magic[MAGICL];
} sAF;

void print_all() {
	int i, j=0;
	for(i=0; i<alloc_len; i++)
		if(alloc[i].buf) {
			iprintf("%s%02d:%03d%s", j?"":"\n", i, alloc[i].size, j?"":" ");
			j^=1;
		}
}

void print_stats() {
	int i, cnt=0;
	size_t sz=0;

	for(i=0; i<alloc_len; i++)
		if(alloc[i].buf) {
			cnt++;
			sz+=alloc[i].size;
		}

	iprintf("\nUsed:%d(+%d*%d+%d*%d=%d)B", sz, cnt, sizeof(sAF), alloc_size, sizeof(sM), sz+cnt*sizeof(sAF)+alloc_size*sizeof(sM));
}

sM *get_empty() {
	int i;
	if(alloc_num>=alloc_len) {
		if(alloc_len>=alloc_size) {
			alloc_size+=ALLOC_GRAIN;
			alloc=realloc(alloc, alloc_size*sizeof(sM));
			if(!alloc) handleoutofmem(__func__, alloc_size*sizeof(sM), __func__, __LINE__);
			bzero(&alloc[alloc_len], ALLOC_GRAIN*sizeof(sM));
		}
		i=alloc_len;
		alloc_len++;
	}
	else {
		for(i=0; i<alloc_len; i++)
			if(!alloc[i].buf)
				break;
	}

	alloc_num++;

	return &alloc[i];
}

void fill(sM al) {
	if(!al.buf) return;

	sAF *af=(sAF *)(al.buf+al.size);
	int i;
	for(i=0; i<MAGICL; i++) af->magic[i]=MAGIC;
}

int check(sM al) {
	if(!al.buf) return 1;

	sAF *af=(sAF *)(al.buf+al.size);
	int i;
	for(i=0; i<MAGICL; i++) if(af->magic[i]!=MAGIC) return 1;

	return 0;
}

void check_all() {
	int i;
	for(i=0; i<alloc_len; i++)
		if(alloc[i].buf && check(alloc[i]))
			iprintf("\nbuffer overflow !! in %d", i);
}
#endif

void *XMALLOC_DECL(_xmalloc)(size_t size, const char *text, u32 line) {
#if defined(XMALLOC_DEBUG_CHECK)
	void *ret=malloc(size+sizeof(sAF));
	if(!ret) handleoutofmem(text, size, __func__, line);

	sM *al=get_empty();
	al->buf=ret;
	al->size=size;

	fill(*al);

	return ret;
#else
	void *ret=malloc(size);
	if(!ret) handleoutofmem(text, size, __func__, line);
	return ret;
#endif
}

void *XMALLOC_DECL(_xcalloc)(size_t num, size_t size, const char *text, u32 line) {
#if defined(XMALLOC_DEBUG_CHECK)
	void *ret=XMALLOC_DECL(_xmalloc)(num*size, text, line);
	bzero(ret, num*size);
	return ret;
#else
	void *ret=calloc(num, size);
	if(!ret) handleoutofmem(text, num*size, __func__, line);
	return ret;
#endif
}

void *XMALLOC_DECL(_xrealloc)(void *ptr, size_t size, const char *text, u32 line) {
#if defined(XMALLOC_DEBUG_CHECK)
	if(!size) return NULL;  // FIXME : i have to free the pointer ?
	if(!ptr) return XMALLOC_DECL(_xmalloc)(size, text, line);

	u32 i;
	for(i=0; i<alloc_len; i++) if(alloc[i].buf==ptr) break;
	if(i==alloc_len) {
		printf("ERROR!r(%#x,%d)!(%s:%d)\n\n", (unsigned int)ptr, size, text, line);
		scanKeys();
		while(1) {
			scanKeys();
			if(keysDown() & KEY_L) break;
			if(keysDown() & KEY_R) print_all();
//					swiWaitForVBlank();
		}
		return NULL;
	}
	else {
		if(check(alloc[i])) {
			printf("/!verif\'%#x:%d\'%s:%d\'%s\\", (unsigned int)alloc[i].buf, alloc[i].size, text, line, __func__);
			scanKeys();
			while(!(keysDown() & KEY_L)) scanKeys();
		}

		void *ret=realloc(ptr, size+sizeof(sAF));
		if(!ret) handleoutofmem(text, size, __func__, line);

		alloc[i].buf=ret;
		alloc[i].size=size;

		fill(alloc[i]);

		return ret;
	}
#else
	void *ret=realloc(ptr, size);
	if(!ret && size) handleoutofmem(text, size, __func__, line);
	return ret;
#endif
}

void XMALLOC_DECL(_xfree)(void *ptr, const char *text, u32 line) {
	if(!ptr) printf("ERROR:xfree was called with a NULL pointer in \"%s:%d\"\n", text, line);
#if defined(XMALLOC_DEBUG_CHECK)
	else {
		u32 i;
		for(i=0; i<alloc_len; i++) if(alloc[i].buf==ptr) break;
		if(i==alloc_len) {
			printf("ERROR!f(%#x)!(%s:%d)\n\n", (unsigned int)ptr, text, line);
			scanKeys();
			while(1) {
				scanKeys();
				if(keysDown() & KEY_L) break;
				if(keysDown() & KEY_R) print_all();
//				swiWaitForVBlank();
			}
		}
		else {
			if(check(alloc[i])) {
				printf("<!verif\'%#x:%d\'%s:%d\'%s>", (unsigned int)alloc[i].buf, alloc[i].size, text, line, __func__);
				scanKeys();
				while(!(keysDown() & KEY_L)) scanKeys();
			}

			bzero(&alloc[i], sizeof(sM));
			alloc_num--;
			if(i==alloc_len-1)
				while(alloc_len>0 && !alloc[alloc_len-1].buf)
					alloc_len--;

			free(ptr);
		}
	}
#else
	else free(ptr);
#endif
}

#undef xmalloc
#undef xcalloc
#undef xrealloc
#undef xfree

// accessible by address
void *xmalloc(size_t size) {
    return _xmalloc(size, "dunno", 0);
}

void *xcalloc(size_t num, size_t size) {
    return _xcalloc(num, size, "dunno", 0);
}

void *xrealloc(void *ptr, size_t size) {
    return _xrealloc(ptr, size, "dunno", 0);
}

void xfree(void *ptr) {
    _xfree(ptr, "dunno", 0);
}

